


# pd.melt() 是 Pandas 中用于数据重塑的核心函数，可将宽格式数据（多列存储相同类型数据）
#               转换为长格式（单列存储多变量值），适用于数据清洗、可视化预处理及统计分析
# 宽表 vs 长表
# 宽表：每行代表一个实体（如学生），多列存储同类数据（如不同科目成绩）。
# 长表：每行代表一个实体的单个观测值（如学生+科目组合），两列分别存储变量名（科目）和值（成绩）
# 典型场景
# 数据可视化（Seaborn/ggplot2 要求长格式）
# 时间序列分析（多日期列转单日期列）
# 数据库存储（减少冗余列）
# melt() 保留原始缺失值（NaN），需后续手动处理

pd.melt(
    frame,                 # 待处理的DataFrame
    id_vars=None,          # 保留的标识列（如ID、姓名）
    value_vars=None,       # 需转换的列（默认所有非id_vars列）
    var_name='variable',   # 存储原列名的列名称
    value_name='value',    # 存储值的列名称
    col_level=None,        # 多级列索引时指定层级
    ignore_index=True      # 是否重置索引（默认True）
)

# id_vars：保留的列（不会被转换），支持单列名或列表。
# value_vars：指定需“融化”的列，不指定则默认处理所有非id_vars列。
# col_level：处理多级列索引（MultiIndex）时，指定转换的层级

import pandas as pd

# 创建宽格式数据
data = {
    'Student': ['Alice', 'Bob'],
    'Math': [90, 85],
    'Science': [92, 88]
}
df = pd.DataFrame(data)

# 转换成长格式
melted_df = pd.melt(
    df,
    id_vars=['Student'],            # 保留学生列
    value_vars=['Math', 'Science'], # 转换的科目列
    var_name='Subject',             # 新列名：存储科目名称
    value_name='Score'              # 新列名：存储分数
)

print(melted_df)

#   Student  Math  Science
# 0   Alice    90       92
# 1     Bob    85       88
# -------------------------------------
#   Student  Subject  Score
# 0   Alice     Math     90
# 1     Bob     Math     85
# 2   Alice  Science     92
# 3     Bob  Science     88







# 创建含多级列的数据
df_multi = pd.DataFrame({
    ('Info', 'Name'): ['Alice', 'Bob'],
    ('Grades', 'Math'): [90, 85],
    ('Grades', 'Science'): [92, 88]
})

# 指定列层级转换
melted_multi = pd.melt(
    df_multi,
    id_vars=[('Info', 'Name')],        # <----------- 这 1 列不变
    value_vars=[('Grades', 'Math'), ('Grades', 'Science')],    # <------- 这2列需要平展成1列
    col_level=1,              # 使用第二级列名（Math/Science）  ---------------  这里会有问题--
    var_name='Subject',    # <---------- 新列明叫 subject
    value_name='Score'     # <---------- 这两列平展后原来的值放到这一列
)

print(melted_multi)

#     Info Grades        
#     Name   Math Science
# 0  Alice     90      92
# 1    Bob     85      88














# 与pivot()的逆操作关系：melt() 是 pivot() 的逆向过程，二者常配合使用
# 长表转宽表
pivot_df = melted_df.pivot(index='Student', columns='Subject', values='Score')


# stack()：将列索引转为行索引，输出MultiIndex序列，保留原始索引。
# melt()：生成新列存储变量名和值，不保留原始索引（除非ignore_index=False）

# 性能优化：
# 指定value_vars避免转换无关列，提升大表处理效率。
# 对多级列索引明确指定col_level，避免歧义









import pandas as pd

# 创建长格式数据（原始结构）
data_long = pd.DataFrame({
    'Student': ['Alice', 'Alice', 'Bob', 'Bob'],
    'Subject': ['Math', 'Physics', 'Math', 'Physics'],
    'Score': [90, 78, 85, 88]
})
print("原始长格式数据：")
print(data_long)
# 原始长格式数据：
#   Student  Subject  Score
# 0   Alice     Math     90
# 1   Alice  Physics     78
# 2     Bob     Math     85
# 3     Bob  Physics     88


# 长表 → 宽表
data_wide = data_long.pivot(
    index='Student',    # 行索引（保留列）
    columns='Subject',  # 列索引（原值转列名）
    values='Score'      # 填充值
).reset_index()         # 将索引还原为列
print("\n转换后的宽格式：")
print(data_wide)
# 转换后的宽格式：
# Subject Student  Math  Physics
# 0         Alice    90       78
# 1           Bob    85       88


# 宽表 → 长表（逆向操作）
data_restored = pd.melt(
    data_wide,
    id_vars=['Student'],          # 保留标识列
    value_vars=['Math', 'Physics'],  # 待合并的列
    var_name='Subject',           # 新变量列名（原列名）
    value_name='Score'            # 新值列名（原单元格值）
)
print("\n还原后的长格式：")
print(data_restored)
# 还原后的长格式：
#   Student  Subject  Score
# 0   Alice     Math     90
# 1     Bob     Math     85
# 2   Alice  Physics     78
# 3     Bob  Physics     88

































# pd.cut 用于将连续数值数据分割为离散区间（分箱），适用于：
#     数据分组分析（如年龄分段、收入分级）
#     特征工程（将连续特征转为分类特征，提升模型效果）
#     数据可视化（生成直方图或分组统计）
#     周期性分析（如时间序列数据的分时段统计）
#     区间宽度固定，但数据量可能不均



import pandas as pd
import numpy as np

# 创建示例数据
data = pd.Series([18, 22, 25, 30, 35, 40, 45, 50])
# 分箱：分成3个等宽区间，并指定标签
result = pd.cut(
    data, 
    bins=3, 
    labels=["青年", "中年", "老年"],
    include_lowest=True  # 确保最小值18被包含
)
print(result)
# (17.999, 30.0], (30.0, 40.0], (40.0, 50.0]



# cut：等宽但不均等数量  根据业务需求平衡分箱粒度
pd.cut([1, 2, 3, 100], bins=2)  # 区间：(0.999, 50.5], (50.5, 100]
# 范围（range）：max - min = 100 - 1 = 99
# 指定分箱数 bins=2，表示将数据分为 2个等宽区间  每个区间的宽度  49.5
# 第一个区间的右边界：1+49.5=50.5  左开右闭 区间（即 (a, b]）：区间1：(0.999, 50.5] （左边界扩展至略小于最小值 1，以确保包含 1）
# 第二个区间的右边界：1+99=100  区间2：(50.5, 100.0]
# 左边界扩展：Pandas 会将第一个区间的左边界设为略小于最小值（此处为 0.999），以确保最小值被包含在第一个区间内

# ---------------------- 参数 -----------------------
# age_bins = [18, 30, 40, 50, 65, 100]
# bins=age_bins    自定义边界分箱

# labels = ['青年', '壮年', '中年', '中老年', '老年']   带标签的分箱
# labels=False --- >   返回的 是分箱区间的索引
# right=False  使用左闭右开 [)  ---  True时区间为(a,b]，False时为[a,b)
# include_lowest=True  包含最低值边界   , 最低 --> []   其他(]
# precision=0  整数精度显示
# duplicates='drop'  处理重复边界






# qcut：等频但区间不等宽 在数据分布未知时，可先用qcut探索等频分组，再通过cut细化业务区间
# 间宽度可能不等，但数据量均匀
# 数据分布偏斜时，qcut仍保证每箱数据量均衡；
# 避免传统等宽分箱导致“高风险”组数据过少的问题

pd.qcut([1, 2, 3, 100], q=2)  # 区间：[1, 2.5], (2.5, 100]

# q=4 表示4个等频区间（四分位）；
# q=[0, 0.3, 0.7, 1] 自定义分位点，区间数据比例约为30%、40%、30%

import pandas as pd
import numpy as np

# 生成100个年龄数据（18-100岁）
np.random.seed(42)
ages = np.random.randint(18, 100, 100)
df = pd.DataFrame({'Age': ages})

# 四分位分箱，自定义标签
df['Age_Group'] = pd.qcut(
    df['Age'], 
    q=4, 
    labels=['青年', '壮年', '中年', '老年']
)

# 验证各区数据量
print(df['Age_Group'].value_counts())

# 计算分割位：index = (n-1) * q（n=10） n表示数量




# 数据含重复值（如收入数据）
incomes = [20, 20, 25, 30, 35, 40, 45, 50, 50, 50]
df = pd.DataFrame({'Income': incomes})

# 自定义分位点，处理重复边界
result, bins = pd.qcut(
    df['Income'],
    q=[0, 0.3, 0.6, 1],  # 分位点
    labels=['低收入', '中收入', '高收入'],
    duplicates='drop',    # 自动去重边界
    retbins=True
)
# 边界值冲突分析
# 若按理论边界分箱：
#     (20, 25]：包含数据 [20, 20, 25] → 3 个
#     (25, 40]：包含数据 [30, 35, 40] → 3 个
#     (40, 50]：包含数据 [45, 50, 50, 50] → 4 个
# 问题：边界值 40 同时属于第 2 区间（右闭合包含 40）和第 3 区间（左开放从 40 开始），导致数据归属冲突
# duplicates='drop' 的干预
#     参数 duplicates='drop' 检测到边界冲突（40.0 被重复使用），自动删除冲突的边界值 40.0。
#     剩余有效边界：[20.0, 25.0, 50.0]（仅保留不冲突的边界）
#  (25, 30, 35, 40, 45, 50, 50, 50] 按分位比例拆分为两个子区间
#         区间 1（中收入）：覆盖理论中 30%~60% 的数据 → (25, 40] → 数据 [30, 35, 40]
#         子区间 2（高收入）：覆盖理论中 60%~100% 的数据 → (40, 50] → 数据 [45, 50, 50, 50]
# q=0.6 位置 5.4 → 值 = 40+(45−40)×0.4=42.0
# q=0.3 位置 2.7 → 值 = 25+(30−25)×0.7=28.5




















# IntervalIndex 是由一系列区间对象 (pd.Interval) 组成的索引。每个区间都有：
#     左边界;右边界;开闭属性 (closed：'left', 'right', 'both', 'neither')

import pandas as pd

# 方法1: 从边界点创建
breaks = [18, 25, 35, 50, 65]
interval_index = pd.IntervalIndex.from_breaks(breaks, closed='right')
print("从边界点创建:")
print(interval_index)

# 方法2: 从元组列表创建
tuples = [(18, 25), (25, 35), (35, 50), (50, 65)]
interval_index = pd.IntervalIndex.from_tuples(tuples, closed='left')
print("\n从元组创建:")
print(interval_index)

# 方法3: 从左右边界数组创建
left = [18, 25, 35, 50]
right = [25, 35, 50, 65]
interval_index = pd.IntervalIndex.from_arrays(left, right, closed='both')
print("\n从左右边界创建:")
print(interval_index)




# 创建预定义的年龄区间
age_intervals = pd.IntervalIndex.from_breaks(
    [18, 30, 45, 60, 75],
    closed='right'
)

# 创建测试数据
ages = [22, 28, 35, 42, 55, 62, 70]

# 使用预定义区间分箱
result = pd.cut(ages, bins=age_intervals)

print("预定义区间:", age_intervals)
print("分箱结果:", result)



# 创建非等宽、混合开闭属性的区间
intervals = [
    pd.Interval(0, 18, closed='neither'),   # 不包含0和18
    pd.Interval(18, 30, closed='left'),     # 包含18，不包含30
    pd.Interval(30, 45, closed='right'),    # 不包含30，包含45
    pd.Interval(45, 100, closed='both')     # 包含45和100
]
# 创建IntervalIndex
custom_index = pd.IntervalIndex(intervals)








ages = np.random.randint(18, 70, size=9, dtype=np.int32)
ages.astype(np.int32).tolist()